/* Copyright 2007 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 * Marcus Cuda
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the dnAnalytics Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using dnAnalytics.Properties;

namespace dnAnalytics.LinearAlgebra
{
    /// <summary>
    /// A matrix class using sparse storage.
    /// </summary>
    [Serializable]
    public partial class SparseMatrix : Matrix
    {
        /// <summary>
        /// Creates a matrix with the given <paramref name="order"/>. The matrix values are the set to zero.
        /// </summary>
        /// <param name="order">The order of the new matrix.</param>
        /// <exception cref="ArgumentException">If either <paramref name="order"/> is less than one.</exception>
        public SparseMatrix(int order) : this(order, order)
        {}

        /// <summary>
        /// Creates a new instance of the <see cref="SparseMatrix"/> class.
        /// </summary>
        /// <remarks>
        /// When creating a new <see cref="SparseMatrix"/> based on another matrix
        /// one should consider carefully how many non-zero values there are in the
        /// <paramref name="source"/> matrix. The usage of a sparse matrix only makes
        /// sense if the matrix is actually 'sparse', i.e. contains relatively few 
        /// non-zero values.
        /// </remarks>
        /// <param name="source">
        /// The <see cref="Matrix"/> from which the data will
        /// be copied to the new instance.
        /// </param>
        public SparseMatrix(Matrix source) : base(source.Rows, source.Columns)
        {
            mRowIndices = new int[Rows];

            for (int i = 0; i < source.Rows; i++)
            {
                for (int j = 0; j < source.Columns; j++)
                {
                    InsertValue(i, j, source[i, j]);
                }
            }
        }

        /// <summary>
        /// Creates a new instance of the <see cref="SparseMatrix"/> class.
        /// </summary>
        /// <param name="source">
        /// The <see cref="SparseMatrix"/> from which the data will
        /// be copied to the new instance.
        /// </param>
        public SparseMatrix(SparseMatrix source) : base(source.Rows, source.Columns)
        {
            mRowIndices = new int[source.mRowIndices.Length];

            // Copy the data
            source.CopyToSparseMatrix(this);
        }

        /// <summary>
        /// Creates a new instance of the <see cref="SparseMatrix"/> class.
        /// </summary>
        /// <remarks>
        /// When creating a new <see cref="SparseMatrix"/> based on an array of doubles
        /// one should consider carefully how many non-zero values there are in the
        /// <paramref name="source"/>. The usage of a sparse matrix only makes
        /// sense if the matrix is actually 'sparse', i.e. contains relatively few 
        /// non-zero values.
        /// </remarks>
        /// <param name="source">The array of doubles from which the data will be
        /// copied to the new instance.</param>
        public SparseMatrix(double[,] source) : base(source.GetLength(0), source.GetLength(1))
        {
            mRowIndices = new int[Rows];

            for (int i = 0; i < source.GetLength(0); i++)
            {
                for (int j = 0; j < source.GetLength(1); j++)
                {
                    InsertValue(i, j, source[i, j]);
                }
            }
        }

        /// <summary>
        /// Creates a <strong>Matrix</strong> for the given number of rows and columns.
        /// </summary>
        /// <param name="numberOfRows">The number of rows.</param>
        /// <param name="numberOfColumns">The number of columns.</param>
        /// <returns>
        /// A <strong>Matrix</strong> with the given dimensions.
        /// </returns>
        protected internal override Matrix CreateMatrix(int numberOfRows, int numberOfColumns)
        {
            return new SparseMatrix(numberOfRows, numberOfColumns);
        }

        /// <summary>
        /// Creates a <strong>Vector</strong> with a the given dimension.
        /// </summary>
        /// <param name="size">The size of the vector.</param>
        /// <returns>
        /// A <strong>Vector</strong> with the given dimension.
        /// </returns>
        protected internal override Vector CreateVector(int size)
        {
            return new SparseVector(size);
        }

        /// <summary>
        /// Adds two matrices together and returns the results.
        /// </summary>
        /// <param name="leftSide">One of the matrices to add.</param>
        /// <param name="rightSide">One of the matrices to add.</param>
        /// <returns>The result of the addition.</returns>
        /// <exception cref="NotConformableException">If <paramref name="leftSide"/> and <paramref name="rightSide"/> don't have the same dimensions.</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator +(SparseMatrix leftSide, SparseMatrix rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (leftSide.Rows != rightSide.Rows || leftSide.Columns != rightSide.Columns)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }

            SparseMatrix ret = new SparseMatrix(leftSide);
            ret.Add(rightSide);
            return ret;
        }

        /// <summary>
        /// Negates each element of the matrix.
        /// </summary>
        /// <param name="rightSide">The matrix to negate.</param>
        /// <returns>A matrix containing the negated values.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator -(SparseMatrix rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            SparseMatrix ret = new SparseMatrix(rightSide);
            ret.Negate();
            return ret;
        }

        /// <summary>
        /// Subtracts two matrices and returns the results.
        /// </summary>
        /// <param name="leftSide">The matrix to subtract from.</param>
        /// <param name="rightSide">The matrix to subtract.</param>
        /// <returns>The result of the subtraction.</returns>
        /// <exception cref="NotConformableException">If <paramref name="leftSide"/> and <paramref name="rightSide"/> don't have the same dimensions.</exception>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator -(SparseMatrix leftSide, SparseMatrix rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (leftSide.Rows != rightSide.Rows || leftSide.Columns != rightSide.Columns)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }

            SparseMatrix ret = new SparseMatrix(leftSide);
            ret.Subtract(rightSide);
            return ret;
        }

        /// <summary>
        /// Divides a <strong>Matrix</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The matrix to divide.</param>
        /// <param name="rightSide">The constant to divide the matrix by.</param>
        /// <returns>The result of the division.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator /(SparseMatrix leftSide, double rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }
            SparseMatrix ret = new SparseMatrix(leftSide);
            ret.Divide(rightSide);
            return ret;
        }

        /// <summary>
        /// Multiplies a <strong>Matrix</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The matrix to multiply.</param>
        /// <param name="rightSide">The constant to multiply the matrix by.</param>
        /// <returns>The result of the multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator *(SparseMatrix leftSide, double rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }
            SparseMatrix ret = new SparseMatrix(leftSide);
            ret.Multiply(rightSide);
            return ret;
        }

        /// <summary>
        /// Multiplies a <strong>Matrix</strong> by a constant and returns the result.
        /// </summary>
        /// <param name="leftSide">The matrix to multiply.</param>
        /// <param name="rightSide">The constant to multiply the matrix by.</param>
        /// <returns>The result of the multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightSide"/> is <see langword="null" />.</exception>
        public static SparseMatrix operator *(double leftSide, SparseMatrix rightSide)
        {
            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }
            SparseMatrix ret = new SparseMatrix(rightSide);
            ret.Multiply(leftSide);
            return ret;
        }

        /// <summary>
        /// Multiplies two matrices.
        /// </summary>
        /// <param name="leftSide">One of the matrices to multiply.</param>
        /// <param name="rightSide">One of the matrices to multiply.</param>
        /// <returns>The result of multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        /// <exception cref="NotConformableException">If the dimensions of <paramref name="leftSide"/> or <paramref name="rightSide"/> don't conform.</exception>
        public static SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }

            if (leftSide.Columns != rightSide.Rows)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }
            return (SparseMatrix)leftSide.Multiply(rightSide);
        }

        /// <summary>
        /// Multiplies a <strong>Matrix</strong> and a <see cref="Vector"/>.
        /// </summary>
        /// <param name="leftSide">The matrix to multiply.</param>
        /// <param name="rightSide">The vector to multiply.</param>
        /// <returns>The result of multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        /// <exception cref="NotConformableException">If <strong>leftSide.Columns ! = rightSide.Count</strong>.</exception>
        public static SparseVector operator *(SparseMatrix leftSide, SparseVector rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }

            if (leftSide.Columns != rightSide.Count)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }

            return (SparseVector)leftSide.Multiply(rightSide);
        }

        /// <summary>
        /// Multiplies a <see cref="Vector"/> and a <strong>Matrix</strong>.
        /// </summary>
        /// <param name="leftSide">The vector to multiply.</param>
        /// <param name="rightSide">The matrix to multiply.</param>
        /// <returns>The result of multiplication.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="leftSide"/> or <paramref name="rightSide"/> is <see langword="null" />.</exception>
        /// <exception cref="NotConformableException">If <strong>leftSide.Count != rightSide.Rows</strong>.</exception>
        public static SparseVector operator *(SparseVector leftSide, SparseMatrix rightSide)
        {
            if (leftSide == null)
            {
                throw new ArgumentNullException("leftSide");
            }

            if (rightSide == null)
            {
                throw new ArgumentNullException("rightSide");
            }

            if (leftSide.Count != rightSide.Rows)
            {
                throw new NotConformableException(Resources.ParametersNotConformable);
            }

            return (SparseVector)rightSide.LeftMultiply(leftSide);
        }
    }
}

